feat: implement service-i18n and service-realtime (Phase 4c)#728
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
- service-i18n: FileI18nAdapter with file-based JSON locale loading, dot-notation key resolution, parameter interpolation, fallback locale support (20 tests) - service-realtime: InMemoryRealtimeAdapter with channel-based pub/sub, event type/object filtering, subscription limits (14 tests) - Both packages follow existing service-* patterns with Plugin + Adapter architecture - Updated ROADMAP.md contract matrix and package status Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
This PR implements two new service packages (@objectstack/service-i18n and @objectstack/service-realtime) to provide production-ready implementations of the II18nService and IRealtimeService contracts. Both packages follow the established adapter+plugin pattern used by existing services (cache, queue, job, storage).
Changes:
- Adds
@objectstack/service-i18nwith file-based locale loading, dot-notation key resolution, parameter interpolation, and fallback locale support (20 tests) - Adds
@objectstack/service-realtimewith in-memory pub/sub, channel routing, event filtering, and error isolation (14 tests) - Updates ROADMAP.md contract implementation count from 11 to 13 (52%) and adds both packages to the Package Status Matrix
Reviewed changes
Copilot reviewed 13 out of 14 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/services/service-i18n/package.json | Package configuration matching service-cache/queue/job/storage pattern with workspace dependencies |
| packages/services/service-i18n/tsconfig.json | TypeScript config extending root tsconfig with src→dist compilation settings |
| packages/services/service-i18n/src/index.ts | Public API exports for I18nServicePlugin and FileI18nAdapter |
| packages/services/service-i18n/src/i18n-service-plugin.ts | Plugin implementing kernel registration pattern via ctx.registerService('i18n', adapter) |
| packages/services/service-i18n/src/file-i18n-adapter.ts | II18nService implementation with JSON file loading, nested key resolution, and parameter interpolation |
| packages/services/service-i18n/src/file-i18n-adapter.test.ts | Comprehensive test suite (20 tests) covering all adapter features including edge cases |
| packages/services/service-realtime/package.json | Package configuration matching other service packages |
| packages/services/service-realtime/tsconfig.json | TypeScript config extending root tsconfig |
| packages/services/service-realtime/src/index.ts | Public API exports for RealtimeServicePlugin and InMemoryRealtimeAdapter |
| packages/services/service-realtime/src/realtime-service-plugin.ts | Plugin implementing kernel registration pattern via ctx.registerService('realtime', adapter) |
| packages/services/service-realtime/src/in-memory-realtime-adapter.ts | IRealtimeService implementation with Map-backed pub/sub, subscription filtering, and maxSubscriptions limit |
| packages/services/service-realtime/src/in-memory-realtime-adapter.test.ts | Test suite (14 tests) covering subscription lifecycle, filtering, and error handling |
| ROADMAP.md | Updates contract implementation count (11→13, 52%), marks both services as complete, and adds Package Status Matrix entries |
| pnpm-lock.yaml | Lockfile updates for two new workspace packages with their dependencies |
Files not reviewed (1)
- pnpm-lock.yaml: Language not supported
| async publish(event: RealtimeEventPayload): Promise<void> { | ||
| // Deliver to all channel subscriptions that match filters | ||
| for (const sub of this.subscriptions.values()) { | ||
| if (this.matchesSubscription(event, sub)) { | ||
| try { | ||
| await sub.handler(event); | ||
| } catch { | ||
| // Swallow handler errors to avoid breaking the publish loop | ||
| } | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
The publish method iterates through ALL subscriptions regardless of channel, making the channelIndex unused. This means events are broadcast to all subscribers system-wide, even if they subscribed to different channels. For proper pub/sub semantics, consider limiting event delivery to subscriptions on matching channels. The channelIndex is built but never consulted during publish.
| await realtime.unsubscribe(sub1); | ||
| expect(realtime.getChannels()).not.toContain('records'); | ||
| }); | ||
| }); |
There was a problem hiding this comment.
Add a test case to verify that subscriptions to different channels don't receive each other's events. This would catch the channel isolation issue in the publish method where it currently broadcasts to all subscriptions regardless of channel.
Implements
II18nServiceandIRealtimeServicecontracts as newservice-*packages, following existing adapter+plugin patterns from service-cache/queue/job/storage.@objectstack/service-i18nFileI18nAdapter— Loads{locale}.jsonfiles from disk, dot-notation key resolution,{{param}}interpolation, fallback locale chainI18nServicePlugin— Registers as'i18n'service on kernel init@objectstack/service-realtimeInMemoryRealtimeAdapter— Map-backed pub/sub with channel routing,object/eventTypessubscription filtering, configurablemaxSubscriptions, handler error isolationRealtimeServicePlugin— Registers as'realtime'service on kernel initROADMAP.md
Usage
✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.